﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DynamicProperties
{
	/// <summary>
	/// Container of dynamic properties for one reference type object.
	/// </summary>
	internal class DynamicPropertyContainer
	{
		private Dictionary<DynamicProperty, object> values;
		private object syncLock = new object();

		public DynamicPropertyContainer()
		{
			this.values = new Dictionary<DynamicProperty, object>();
		}

		/// <summary>
		/// Sets dynamic property value to this container.
		/// </summary>
		/// <param name="target">Target object.</param>
		/// <param name="property">Property to write.</param>
		/// <param name="value">Value to set.</param>
		public void SetProperty(object target, DynamicProperty property, object value)
		{
			if (target == null)
				throw new ArgumentNullException("target");

			if (property == null)
				throw new ArgumentNullException("property");

			lock(syncLock)
			{
				object oldValue;

				// get old value
				if (!this.values.TryGetValue(property, out oldValue))
				{
					// if not present, use default value as old value
					oldValue = property.GetDefaultValue();
				}

				// set property
				this.values[property] = value;
				
				// notify change
				OnPropertyChanged(target, property, oldValue, value);
			}
		}

		/// <summary>
		/// Gets dynamic property from this container if property is set.
		/// </summary>
		/// <param name="target">Target object.</param>
		/// <param name="property">Property to read.</param>
		/// <param name="result">Result of query.</param>
		/// <returns>Returns true if value for given property was found. False is returned if value is null or not defined yet.</returns>
		public bool TryGetProperty(object target, DynamicProperty property, out object result)
		{
			if (target == null)
				throw new ArgumentNullException("target");

			if (property == null)
				throw new ArgumentNullException("property");

			lock (syncLock)
			{
				// try to get property value
				bool retVal = this.values.TryGetValue(property, out result);

				return retVal;
			}
		}

		/// <summary>
		/// Gets dynamic property from this container if property is set. If not set, default value is fetched and stored.
		/// </summary>
		/// <param name="target">Target object.</param>
		/// <param name="property">Property to read.</param>
		/// <returns>Returns value of property.</returns>
		public object GetProperty(object target, DynamicProperty property)
		{
			if (target == null)
				throw new ArgumentNullException("target");

			if (property == null)
				throw new ArgumentNullException("property");

			object result;

			lock (syncLock)
			{
				// try to get property value
				if (!this.values.TryGetValue(property, out result))
				{
					// if not found, fetch default value
					result = property.GetDefaultValue();
					
					if (result != null) // don't store null values
					{
						SetProperty(target, property, result);
					}
				}
			}

			return result;
		}

		/// <summary>
		/// Removes value pro specific property.
		/// </summary>
		/// <param name="target">Target object.</param>
		/// <param name="property">Property to remove.</param>
		/// <returns>Returns if property was found.</returns>
		public bool RemoveProperty(object target, DynamicProperty property)
		{
			if (property == null)
				throw new ArgumentNullException("property");

			lock (syncLock)
			{
				bool result = false;
				object oldValue;

				// is this property set?
				if(this.values.TryGetValue(property, out oldValue))
				{
					// remove value
					this.values.Remove(property);

					// property value found
					result = true;

					// notify change (back to default value)
					OnPropertyChanged(target, property, oldValue, property.GetDefaultValue());
				}

				return result;
			}
		}

		internal protected void OnPropertyChanged(object target, DynamicProperty property, object oldValue, object newValue)
		{
			// notify property
			property.OnPropertyUpdated(target, oldValue, newValue);
		}

		/// <summary>
		/// Dumps all values of non-default values.
		/// </summary>
		/// <returns></returns>
		internal DynamicPropertyDump[] DumpAll()
		{
			lock (syncLock)
			{
				// is empty?
				if (this.values.Count == 0)
					return DynamicPropertyDump.EmptyDumpArray;

				// fetch all values
				DynamicPropertyDump[] result;
				result = this.values.Select(v => new DynamicPropertyDump(v.Key, v.Value)).ToArray();
				return result;
			}
		}
	}
}
